Part1
This exercise concerns the clinical descriptions of tumours from The Cancer Genome Archive. It was previously downloaded from GEO and has undergone some minor alterations. See the script process_tcga_clinical.R.
The data are provided as the file tcga_clinical.tsv in the raw_data directory of the r_crash_course.zip file
Exercise: What function from readr would you use to read the file tcga_clinical.tsv into R? Read the file in. What are the number of rows and columns?
Since the input file is a “tab-separated” file we need to use read_tsv. Before we can use the function we need to load the readr library.
### Remember that every time we want to use a function from a particular package, that package needs to be loaded from our library
library(readr)
data <- read_tsv("raw_data/tcga_clinical.tsv")
One or more parsing issues, see `problems()` for detailsRows: 7706 Columns: 420
-- Column specification -----------------------------------------------------
Delimiter: "\t"
chr (395): bcr_patient_barcode, bcr_patient_uuid, form_completion_date, p...
dbl (23): initial_pathologic_dx_year, age_at_diagnosis, percent_blasts_p...
lgl (2): sarcomatoid_features, sarcomatoid_percent_of_tumor
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
data
You should find that the data frame contains a great deal of columns; far too many to be useful. We would like to keep the columns containing the age of the patient, and the tumour stage in our analysis. Rather than opening-up the file, or Viewing the file in RStudio, we can use a couple of helper functions to identify the relevant column names.
Exercise: Use the select function in conjunction with contains and starts_with to identify columns that have Age or Stage information their name. The code should look like the following (you will need to fill-in the dots).
The functions contains and starts_with perform similar operations when used to select columns from a data frame. To use either, and to use the select function, we first have to load the dplyr library. Using the contains function will identify all columns that have a particular text pattern somewhere in their name. If we wanted all the columns with “age” in the name, the following wouldn’t be a good choice as it would also identify columns with “stage” in.
library(dplyr)
select(data, contains("age"))
But if we wanted all the columns regarding “stage”, contains would be a good choice
select(data, contains("stage"))
Since the age-related columns start with “age” we can use the starts_with function instead.
select(data, starts_with("age"))
select(data, contains("age"),
-contains("stage"),
-contains("agent"),
-contains("heritage"),
-contains("percentage"))
Exercise: Use the select function to create a new data frame that contains the following columns. These are not the actual columns names - Tumour site - Race - Gender - Age at diagnosis - Dead / Alive Status You can add extra columns if you wish
See below for example output
In this case we can list the columns that we want to select. The code below makes use of the pipe %>% which allows the output from one line of code to be used as an input in the next line.
data <- read_tsv("raw_data/tcga_clinical.tsv") %>%
select(tumor_tissue_site,
race,
gender,
age_at_initial_pathologic_diagnosis,
vital_status)
One or more parsing issues, see `problems()` for details
data
Exercise: Use the dplyr function called count to tabulate how which sites are included in the data. Re-arrange the output from count using arrange to determine the most common type of cancer in the dataset.
See below for example output
The count function takes a data frame as it’s first argument, followed by the name of the column that we want to obtain counts for.
The default for count is to report the results in ascending order.
count(data, tumor_tissue_site)
For our particular use-case we want the tissue typesin descending order. Fortunately, there is a convenient function in dplyr that will this. We can also take advantage of the pipe operation to chain the steps together.
count(data, tumor_tissue_site) %>%
arrange(desc(n))
This reveals a problem; the most common entry in the column is NA; a special value in R representing a lack of data. Missing data, and the various ways used to represent it, is sometimes an issue in data analysis. We can also see that the term [Not Available] is sometimes used.
Exercise: Not all samples have an entry for tumour type. Use the filter function to create a table with valid entries for tumor_tissue_site. Create a barplot (geom_bar) or column plot (geom_col) to show display the number of occurences of each tumour type
HINT: An easy way to make the labels on the x-axis more legible is to use the coord_flip function
ggplot(data, aes(x=...)) + geom_bar() + coord_flip()
We can use the filter function to exclude the [Not Available] entries from our data. The == sign can be used to identify entries that are equal to [Not Available], whereas != can be used to identify entries that are not equal to [Not Available].
filter(data, tumor_tissue_site == "[Not Available]")
filter(data, tumor_tissue_site != "[Not Available]")
Removing the NA entries is a bit more tricky, as neither of the lines of code below work as you might expect.
filter(data, tumor_tissue_site == NA)
filter(data, tumor_tissue_site == "NA")
We need to use a special function is.na to identify NA entries, and as above !is.na will identify rows that do not contain an NA
filter(data, !is.na(tumor_tissue_site))
Putting everything together, we get the following R code. We use the geom_col function which requires both the x and y aesthetics.
filter(data,!is.na(tumor_tissue_site)) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
count(tumor_tissue_site) %>%
arrange(desc(n)) %>%
ggplot(aes(x = tumor_tissue_site,y=n)) + geom_col() + coord_flip()

Strictly speaking, we don’t actually need the counting step as these counts required for the plot will be generated automatically if we use `geom_bar’ instead.
filter(data,!is.na(tumor_tissue_site)) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
ggplot(aes(x = tumor_tissue_site)) + geom_bar() + coord_flip()

You might be wondering why the rows are ordering alphabetically rather than in count order. The default ordering for a factor in R is alphabetical.
The forcats package (which of part of tidyverse) can be used if we want a different order to be shown.
Rather than the x axis being mapped to tumor_tissue_site, it can be maped to a re-ordered version of the factor.
...aes(x=fct_reorder(tumor_tissue_site,n))...
library(forcats)
filter(data,!is.na(tumor_tissue_site)) %>%
filter(tumor_tissue_site != "[Not Available]") %>%
count(tumor_tissue_site) %>%
arrange(desc(n)) %>%
ggplot(aes(x = fct_reorder(tumor_tissue_site,n),y=n)) + geom_col() + coord_flip()

LS0tDQp0aXRsZTogIlIgY3Jhc2ggY291cnNlIGV4ZXJjaXNlIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgY3NzOiBzdHlsZXNoZWV0cy9zdHlsZXMuY3NzDQotLS0NCg0KIyBQYXJ0MQ0KDQpUaGlzIGV4ZXJjaXNlIGNvbmNlcm5zIHRoZSBjbGluaWNhbCBkZXNjcmlwdGlvbnMgb2YgdHVtb3VycyBmcm9tIFRoZSBDYW5jZXIgR2Vub21lIEFyY2hpdmUuIEl0IHdhcyBwcmV2aW91c2x5IGRvd25sb2FkZWQgZnJvbSBbR0VPXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlby9xdWVyeS9hY2MuY2dpP2FjYz1HU0U2Mjk0NCkgYW5kIGhhcyB1bmRlcmdvbmUgc29tZSBtaW5vciBhbHRlcmF0aW9ucy4gU2VlIHRoZSBzY3JpcHQgW3Byb2Nlc3NfdGNnYV9jbGluaWNhbC5SXSgvcHJvY2Vzc190Y2dhX2NsaW5pY2FsLlIpLg0KDQpUaGUgZGF0YSBhcmUgcHJvdmlkZWQgYXMgdGhlIGZpbGUgYHRjZ2FfY2xpbmljYWwudHN2YCBpbiB0aGUgYHJhd19kYXRhYCBkaXJlY3Rvcnkgb2YgdGhlIGByX2NyYXNoX2NvdXJzZS56aXBgIGZpbGUNCg0KPGRpdiBjbGFzcz0iZXhlcmNpc2UiPg0KKipFeGVyY2lzZSoqOiBXaGF0IGZ1bmN0aW9uIGZyb20gYHJlYWRyYCB3b3VsZCB5b3UgdXNlIHRvIHJlYWQgdGhlIGZpbGUgYHRjZ2FfY2xpbmljYWwudHN2YCBpbnRvIFI/IFJlYWQgdGhlIGZpbGUgaW4uIFdoYXQgYXJlIHRoZSBudW1iZXIgb2Ygcm93cyBhbmQgY29sdW1ucz8NCg0KPC9kaXY+DQoNClNpbmNlIHRoZSBpbnB1dCBmaWxlIGlzIGEgInRhYi1zZXBhcmF0ZWQiIGZpbGUgd2UgbmVlZCB0byB1c2UgYHJlYWRfdHN2YC4gQmVmb3JlIHdlIGNhbiB1c2UgdGhlIGZ1bmN0aW9uIHdlIG5lZWQgdG8gbG9hZCB0aGUgYHJlYWRyYCBsaWJyYXJ5Lg0KDQpgYGB7cn0NCiMjIyBSZW1lbWJlciB0aGF0IGV2ZXJ5IHRpbWUgd2Ugd2FudCB0byB1c2UgYSBmdW5jdGlvbiBmcm9tIGEgcGFydGljdWxhciBwYWNrYWdlLCB0aGF0IHBhY2thZ2UgbmVlZHMgdG8gYmUgbG9hZGVkIGZyb20gb3VyIGxpYnJhcnkNCg0KbGlicmFyeShyZWFkcikNCmRhdGEgPC0gcmVhZF90c3YoInJhd19kYXRhL3RjZ2FfY2xpbmljYWwudHN2IikNCmRhdGENCmBgYA0KDQoNCllvdSBzaG91bGQgZmluZCB0aGF0IHRoZSBkYXRhIGZyYW1lIGNvbnRhaW5zIGEgZ3JlYXQgZGVhbCBvZiBjb2x1bW5zOyBmYXIgdG9vIG1hbnkgdG8gYmUgdXNlZnVsLiBXZSB3b3VsZCBsaWtlIHRvIGtlZXAgdGhlIGNvbHVtbnMgY29udGFpbmluZyB0aGUgYWdlIG9mIHRoZSBwYXRpZW50LCBhbmQgdGhlIHR1bW91ciBzdGFnZSBpbiBvdXIgYW5hbHlzaXMuIFJhdGhlciB0aGFuIG9wZW5pbmctdXAgdGhlIGZpbGUsIG9yIGBWaWV3YGluZyB0aGUgZmlsZSBpbiBSU3R1ZGlvLCB3ZSBjYW4gdXNlIGEgY291cGxlIG9mIGhlbHBlciBmdW5jdGlvbnMgdG8gaWRlbnRpZnkgdGhlIHJlbGV2YW50IGNvbHVtbiBuYW1lcy4NCg0KPGRpdiBjbGFzcz0iZXhlcmNpc2UiPg0KKipFeGVyY2lzZSoqOiBVc2UgdGhlIGBzZWxlY3RgIGZ1bmN0aW9uIGluIGNvbmp1bmN0aW9uIHdpdGggYGNvbnRhaW5zYCBhbmQgYHN0YXJ0c193aXRoYCB0byBpZGVudGlmeSBjb2x1bW5zIHRoYXQgaGF2ZSBBZ2Ugb3IgU3RhZ2UgaW5mb3JtYXRpb24gdGhlaXIgbmFtZS4gVGhlIGNvZGUgc2hvdWxkIGxvb2sgbGlrZSB0aGUgZm9sbG93aW5nICh5b3Ugd2lsbCBuZWVkIHRvIGZpbGwtaW4gdGhlIGRvdHMpLg0KDQo8L2Rpdj4NCg0KVGhlIGZ1bmN0aW9ucyBgY29udGFpbnNgIGFuZCBgc3RhcnRzX3dpdGhgIHBlcmZvcm0gc2ltaWxhciBvcGVyYXRpb25zIHdoZW4gdXNlZCB0byBzZWxlY3QgY29sdW1ucyBmcm9tIGEgZGF0YSBmcmFtZS4gVG8gdXNlIGVpdGhlciwgYW5kIHRvIHVzZSB0aGUgYHNlbGVjdGAgZnVuY3Rpb24sIHdlIGZpcnN0IGhhdmUgdG8gbG9hZCB0aGUgYGRwbHlyYCBsaWJyYXJ5LiBVc2luZyB0aGUgYGNvbnRhaW5zYCBmdW5jdGlvbiB3aWxsIGlkZW50aWZ5IGFsbCBjb2x1bW5zIHRoYXQgaGF2ZSBhIHBhcnRpY3VsYXIgdGV4dCBwYXR0ZXJuIHNvbWV3aGVyZSBpbiB0aGVpciBuYW1lLiBJZiB3ZSB3YW50ZWQgYWxsIHRoZSBjb2x1bW5zIHdpdGggImFnZSIgaW4gdGhlIG5hbWUsIHRoZSBmb2xsb3dpbmcgd291bGRuJ3QgYmUgYSBnb29kIGNob2ljZSBhcyBpdCB3b3VsZCBhbHNvIGlkZW50aWZ5IGNvbHVtbnMgd2l0aCAic3RhZ2UiIGluLg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpzZWxlY3QoZGF0YSwgY29udGFpbnMoImFnZSIpKQ0KYGBgDQpCdXQgaWYgd2Ugd2FudGVkIGFsbCB0aGUgY29sdW1ucyByZWdhcmRpbmcgInN0YWdlIiwgYGNvbnRhaW5zYCB3b3VsZCBiZSBhIGdvb2QgY2hvaWNlDQoNCmBgYHtyfQ0Kc2VsZWN0KGRhdGEsIGNvbnRhaW5zKCJzdGFnZSIpKQ0KYGBgDQpTaW5jZSB0aGUgYWdlLXJlbGF0ZWQgY29sdW1ucyBzdGFydCB3aXRoICJhZ2UiIHdlIGNhbiB1c2UgdGhlIGBzdGFydHNfd2l0aGAgZnVuY3Rpb24gaW5zdGVhZC4NCg0KYGBge3J9DQpzZWxlY3QoZGF0YSwgc3RhcnRzX3dpdGgoImFnZSIpKQ0KYGBgDQpgYGB7cn0NCnNlbGVjdChkYXRhLCBjb250YWlucygiYWdlIiksIA0KICAgICAgIC1jb250YWlucygic3RhZ2UiKSwgDQogICAgICAgLWNvbnRhaW5zKCJhZ2VudCIpLA0KICAgICAgIC1jb250YWlucygiaGVyaXRhZ2UiKSwNCiAgICAgICAtY29udGFpbnMoInBlcmNlbnRhZ2UiKSkNCmBgYA0KDQoNCjxkaXYgY2xhc3M9ImV4ZXJjaXNlIj4NCioqRXhlcmNpc2U6KiogVXNlIHRoZSBgc2VsZWN0YCBmdW5jdGlvbiB0byBjcmVhdGUgYSBuZXcgZGF0YSBmcmFtZSB0aGF0IGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgY29sdW1ucy4gKipUaGVzZSBhcmUgbm90IHRoZSBhY3R1YWwgY29sdW1ucyBuYW1lcyoqDQogIC0gVHVtb3VyIHNpdGUNCiAgLSBSYWNlDQogIC0gR2VuZGVyDQogIC0gQWdlIGF0IGRpYWdub3Npcw0KICAtIERlYWQgLyBBbGl2ZSBTdGF0dXMNCllvdSBjYW4gYWRkIGV4dHJhIGNvbHVtbnMgaWYgeW91IHdpc2gNCg0KKipTZWUgYmVsb3cgZm9yIGV4YW1wbGUgb3V0cHV0KioNCjwvZGl2Pg0KDQpJbiB0aGlzIGNhc2Ugd2UgY2FuIGxpc3QgdGhlIGNvbHVtbnMgdGhhdCB3ZSB3YW50IHRvIHNlbGVjdC4gVGhlIGNvZGUgYmVsb3cgbWFrZXMgdXNlIG9mIHRoZSBwaXBlIGAgJT4lIGAgd2hpY2ggYWxsb3dzIHRoZSBvdXRwdXQgZnJvbSBvbmUgbGluZSBvZiBjb2RlIHRvIGJlIHVzZWQgYXMgYW4gaW5wdXQgaW4gdGhlIG5leHQgbGluZS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmRhdGEgPC0gcmVhZF90c3YoInJhd19kYXRhL3RjZ2FfY2xpbmljYWwudHN2IikgJT4lIA0KICAgICAgc2VsZWN0KHR1bW9yX3Rpc3N1ZV9zaXRlLA0KICAgICAgICAgICAgICAgIHJhY2UsDQogICAgICAgICAgICAgICAgZ2VuZGVyLA0KICAgICAgICAgICAgICAgIGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzLA0KICAgICAgICAgICAgICAgIHZpdGFsX3N0YXR1cykNCmRhdGENCmBgYA0KDQoNCg0KPGRpdiBjbGFzcz0iZXhlcmNpc2UiPg0KKipFeGVyY2lzZToqKiBVc2UgdGhlIGBkcGx5cmAgZnVuY3Rpb24gY2FsbGVkIGBjb3VudGAgdG8gdGFidWxhdGUgaG93IHdoaWNoIHNpdGVzIGFyZSBpbmNsdWRlZCBpbiB0aGUgZGF0YS4gUmUtYXJyYW5nZSB0aGUgb3V0cHV0IGZyb20gYGNvdW50YCB1c2luZyBgYXJyYW5nZWAgdG8gZGV0ZXJtaW5lIHRoZSBtb3N0IGNvbW1vbiB0eXBlIG9mIGNhbmNlciBpbiB0aGUgZGF0YXNldC4NCg0KKipTZWUgYmVsb3cgZm9yIGV4YW1wbGUgb3V0cHV0KioNCjwvZGl2Pg0KDQpUaGUgYGNvdW50YCBmdW5jdGlvbiB0YWtlcyBhIGRhdGEgZnJhbWUgYXMgaXQncyBmaXJzdCBhcmd1bWVudCwgZm9sbG93ZWQgYnkgdGhlIG5hbWUgb2YgdGhlIGNvbHVtbiB0aGF0IHdlIHdhbnQgdG8gb2J0YWluIGNvdW50cyBmb3IuDQoNClRoZSBkZWZhdWx0IGZvciBgY291bnRgIGlzIHRvIHJlcG9ydCB0aGUgcmVzdWx0cyBpbiAqYXNjZW5kaW5nKiBvcmRlci4NCg0KYGBge3J9DQpjb3VudChkYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSkNCmBgYA0KRm9yIG91ciBwYXJ0aWN1bGFyIHVzZS1jYXNlIHdlIHdhbnQgdGhlIHRpc3N1ZSB0eXBlc2luICpkZXNjZW5kaW5nKiBvcmRlci4gRm9ydHVuYXRlbHksIHRoZXJlIGlzIGEgY29udmVuaWVudCBmdW5jdGlvbiBpbiBgZHBseXJgIHRoYXQgd2lsbCB0aGlzLiBXZSBjYW4gYWxzbyB0YWtlIGFkdmFudGFnZSBvZiB0aGUgcGlwZSBvcGVyYXRpb24gdG8gY2hhaW4gdGhlIHN0ZXBzIHRvZ2V0aGVyLg0KDQpgYGB7ciB9DQpjb3VudChkYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSkgJT4lIA0KICBhcnJhbmdlKGRlc2MobikpDQpgYGANClRoaXMgcmV2ZWFscyBhIHByb2JsZW07IHRoZSBtb3N0IGNvbW1vbiBlbnRyeSBpbiB0aGUgY29sdW1uIGlzIGBOQWA7IGEgc3BlY2lhbCB2YWx1ZSBpbiBSIHJlcHJlc2VudGluZyBhIGxhY2sgb2YgZGF0YS4gTWlzc2luZyBkYXRhLCBhbmQgdGhlIHZhcmlvdXMgd2F5cyB1c2VkIHRvIHJlcHJlc2VudCBpdCwgaXMgc29tZXRpbWVzIGFuIGlzc3VlIGluIGRhdGEgYW5hbHlzaXMuIFdlIGNhbiBhbHNvIHNlZSB0aGF0IHRoZSB0ZXJtIGBbTm90IEF2YWlsYWJsZV1gIGlzIHNvbWV0aW1lcyB1c2VkLg0KDQo8ZGl2IGNsYXNzPSJleGVyY2lzZSI+DQoqKkV4ZXJjaXNlKio6IE5vdCBhbGwgc2FtcGxlcyBoYXZlIGFuIGVudHJ5IGZvciB0dW1vdXIgdHlwZS4gVXNlIHRoZSBgZmlsdGVyYCBmdW5jdGlvbiB0byBjcmVhdGUgYSB0YWJsZSB3aXRoIHZhbGlkIGVudHJpZXMgZm9yIGB0dW1vcl90aXNzdWVfc2l0ZWAuIENyZWF0ZSBhIGJhcnBsb3QgKGBnZW9tX2JhcmApIG9yIGNvbHVtbiBwbG90IChgZ2VvbV9jb2xgKSB0byBzaG93IGRpc3BsYXkgdGhlIG51bWJlciBvZiBvY2N1cmVuY2VzIG9mIGVhY2ggdHVtb3VyIHR5cGUNCg0KSElOVDogQW4gZWFzeSB3YXkgdG8gbWFrZSB0aGUgbGFiZWxzIG9uIHRoZSB4LWF4aXMgbW9yZSBsZWdpYmxlIGlzIHRvIHVzZSB0aGUgYGNvb3JkX2ZsaXBgIGZ1bmN0aW9uDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpnZ3Bsb3QoZGF0YSwgYWVzKHg9Li4uKSkgKyBnZW9tX2JhcigpICsgY29vcmRfZmxpcCgpDQpgYGANCg0KPC9kaXY+DQoNCldlIGNhbiB1c2UgdGhlIGBmaWx0ZXJgIGZ1bmN0aW9uIHRvIGV4Y2x1ZGUgdGhlIGBbTm90IEF2YWlsYWJsZV1gIGVudHJpZXMgZnJvbSBvdXIgZGF0YS4gVGhlIGA9PWAgc2lnbiBjYW4gYmUgdXNlZCB0byBpZGVudGlmeSBlbnRyaWVzIHRoYXQgKmFyZSogZXF1YWwgdG8gYFtOb3QgQXZhaWxhYmxlXWAsIHdoZXJlYXMgYCE9YCBjYW4gYmUgdXNlZCB0byBpZGVudGlmeSBlbnRyaWVzIHRoYXQgYXJlICpub3QqIGVxdWFsIHRvIGBbTm90IEF2YWlsYWJsZV1gLg0KDQpgYGB7cn0NCmZpbHRlcihkYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSA9PSAiW05vdCBBdmFpbGFibGVdIikNCmBgYA0KYGBge3J9DQpmaWx0ZXIoZGF0YSwgdHVtb3JfdGlzc3VlX3NpdGUgIT0gIltOb3QgQXZhaWxhYmxlXSIpDQpgYGANCg0KUmVtb3ZpbmcgdGhlIGBOQWAgZW50cmllcyBpcyBhIGJpdCBtb3JlIHRyaWNreSwgYXMgbmVpdGhlciBvZiB0aGUgbGluZXMgb2YgY29kZSBiZWxvdyB3b3JrIGFzIHlvdSBtaWdodCBleHBlY3QuDQoNCmBgYHtyfQ0KZmlsdGVyKGRhdGEsIHR1bW9yX3Rpc3N1ZV9zaXRlID09IE5BKQ0KZmlsdGVyKGRhdGEsIHR1bW9yX3Rpc3N1ZV9zaXRlID09ICJOQSIpDQpgYGANCg0KV2UgbmVlZCB0byB1c2UgYSBzcGVjaWFsIGZ1bmN0aW9uIGBpcy5uYWAgdG8gaWRlbnRpZnkgYE5BYCBlbnRyaWVzLCBhbmQgYXMgYWJvdmUgYCFpcy5uYWAgd2lsbCBpZGVudGlmeSByb3dzIHRoYXQgZG8gbm90IGNvbnRhaW4gYW4gYE5BYA0KDQpgYGB7cn0NCmZpbHRlcihkYXRhLCAhaXMubmEodHVtb3JfdGlzc3VlX3NpdGUpKQ0KYGBgDQpQdXR0aW5nIGV2ZXJ5dGhpbmcgdG9nZXRoZXIsIHdlIGdldCB0aGUgZm9sbG93aW5nIFIgY29kZS4gV2UgdXNlIHRoZSBgZ2VvbV9jb2xgIGZ1bmN0aW9uIHdoaWNoIHJlcXVpcmVzIGJvdGggdGhlIGB4YCBhbmQgYHlgIGFlc3RoZXRpY3MuDQoNCmBgYHtyfQ0KICBmaWx0ZXIoZGF0YSwhaXMubmEodHVtb3JfdGlzc3VlX3NpdGUpKSAlPiUgDQogIGZpbHRlcih0dW1vcl90aXNzdWVfc2l0ZSAhPSAiW05vdCBBdmFpbGFibGVdIikgJT4lIA0KICBjb3VudCh0dW1vcl90aXNzdWVfc2l0ZSkgJT4lIA0KICBhcnJhbmdlKGRlc2MobikpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gdHVtb3JfdGlzc3VlX3NpdGUseT1uKSkgKyBnZW9tX2NvbCgpICsgY29vcmRfZmxpcCgpDQpgYGANCg0KU3RyaWN0bHkgc3BlYWtpbmcsIHdlIGRvbid0IGFjdHVhbGx5IG5lZWQgdGhlIGBjb3VudGluZ2Agc3RlcCBhcyB0aGVzZSBjb3VudHMgcmVxdWlyZWQgZm9yIHRoZSBwbG90IHdpbGwgYmUgZ2VuZXJhdGVkIGF1dG9tYXRpY2FsbHkgaWYgd2UgdXNlIGBnZW9tX2JhcicgaW5zdGVhZC4NCg0KYGBge3J9DQogIGZpbHRlcihkYXRhLCFpcy5uYSh0dW1vcl90aXNzdWVfc2l0ZSkpICU+JSANCiAgZmlsdGVyKHR1bW9yX3Rpc3N1ZV9zaXRlICE9ICJbTm90IEF2YWlsYWJsZV0iKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHR1bW9yX3Rpc3N1ZV9zaXRlKSkgKyBnZW9tX2JhcigpICsgY29vcmRfZmxpcCgpDQpgYGANCg0KWW91IG1pZ2h0IGJlIHdvbmRlcmluZyB3aHkgdGhlIHJvd3MgYXJlIG9yZGVyaW5nIGFscGhhYmV0aWNhbGx5IHJhdGhlciB0aGFuIGluIGNvdW50IG9yZGVyLiBUaGUgZGVmYXVsdCBvcmRlcmluZyBmb3IgYSAqZmFjdG9yKiBpbiBSIGlzIGFscGhhYmV0aWNhbC4gDQoNClRoZSBgZm9yY2F0c2AgcGFja2FnZSAod2hpY2ggb2YgcGFydCBvZiBgdGlkeXZlcnNlYCkgY2FuIGJlIHVzZWQgaWYgd2Ugd2FudCBhIGRpZmZlcmVudCBvcmRlciB0byBiZSBzaG93bi4NCg0KLSBbZm9yY2F0cyBwYWNrYWdlIGRlc2NyaXB0aW9uXShodHRwczovL2ZvcmNhdHMudGlkeXZlcnNlLm9yZy8pDQoNClJhdGhlciB0aGFuIHRoZSB4IGF4aXMgYmVpbmcgbWFwcGVkIHRvIGB0dW1vcl90aXNzdWVfc2l0ZWAsIGl0IGNhbiBiZSBtYXBlZCB0byBhIHJlLW9yZGVyZWQgdmVyc2lvbiBvZiB0aGUgZmFjdG9yLg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KLi4uYWVzKHg9ZmN0X3Jlb3JkZXIodHVtb3JfdGlzc3VlX3NpdGUsbikpLi4uDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkoZm9yY2F0cykNCiAgZmlsdGVyKGRhdGEsIWlzLm5hKHR1bW9yX3Rpc3N1ZV9zaXRlKSkgJT4lIA0KICBmaWx0ZXIodHVtb3JfdGlzc3VlX3NpdGUgIT0gIltOb3QgQXZhaWxhYmxlXSIpICU+JSANCiAgY291bnQodHVtb3JfdGlzc3VlX3NpdGUpICU+JSANCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGZjdF9yZW9yZGVyKHR1bW9yX3Rpc3N1ZV9zaXRlLG4pLHk9bikpICsgZ2VvbV9jb2woKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCg==